<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Viev
 * @copyright  Andrey Kucherenko <kucherenko.andrey@gmail.com>
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

class Apk_View_Xslt implements Zend_View_Interface
{
    private $_strictVars = false;
    const KEY_AS_TAG = 1;
    const KEY_AS_ATTRIBUTE = 2;


    private $_rowsetClass = "Apk_Db_Table_Rowset";
    /**
     * Classname for row
     *
     * @var string
     */
    private $_rowClass = 'Apk_Db_Table_Row';
    /**
     * Path stack for script, helper, and filter directories.
     *
     * @var array
     */
    private $_path = array(
    'script' => array(),
    'helper' => array(),
    'filter' => array(),
    );

    /**
     * Instances of helper objects.
     *
     * @var array
     */
    private $_helper = array();

    /**
     * Map of helper => class pairs to help in determining helper class from
     * name
     * @var array
     */
    private $_helperLoaded = array();

    /**
     * Map of helper => classfile pairs to aid in determining helper classfile
     * @var array
     */
    private $_helperLoadedDir = array();
    private $_defaultNodeName = 'row';
    private $_defaultAttrName = 'attr';
    /**
     * XMLWriter object
     *
     * @var XMLWriter
     */
    public $xmlWriter;

    /**
     * Path to xsl template
     *
     * @var string
     */
    protected $_template_path;
    /**
     * Path to xsl templates
     *
     * @var string
     */
    protected $_template_dir;

    /**
     * Конструктор
     *
     * @param string $tmplPath
     * @param array $extraParams
     * @return void
     */
    public function __construct($tmplPath = null, $extraParams = array())
    {
        $this->_startXml();
        if (null !== $tmplPath) {
            $this->setScriptPath($tmplPath);
        }

        foreach ($extraParams as $key => $value) {
            $this->assign($key, $value);
        }
    }

    /**
     * Возвращение объекта шаблонизатора
     *
     * @return Smarty
     */
    public function getEngine()
    {
        return $this;
    }

    /**
     * Установка пути к шаблонам
     *
     * @param string $path Директория, устанавливаемая как путь к шаблонам
     * @return void
     */
    public function setScriptPath($path)
    {
        if (is_readable($path)) {
            $this->_template_path = $path;
            return;
        }

        throw new Exception('Invalid path provided');
    }

    /**
     * Присвоение значения переменной шаблона
     *
     * @param string $key The variable name.
     * @param mixed $val The variable value.
     * @return void
     */
    public function __set($key, $val)
    {
        $this->assign($key, $val);
    }

    /**
     * Accesses a helper object from within a script.
     *
     * If the helper class has a 'view' property, sets it with the current view
     * object.
     *
     * @param string $name The helper name.
     * @param array $args The parameters for the helper.
     * @return string The result of the helper output.
     */
    public function __call($name, $args)
    {
        // is the helper already loaded?
        $helper = $this->getHelper($name);

        // call the helper method
        return call_user_func_array(
        array($helper, $name),
        $args
        );
    }

    /**
     * Get a helper by name
     *
     * @param  string $name
     * @return object
     */
    public function getHelper($name)
    {
        if (!isset($this->_helper[$name])) {
            $class = $this->_loadClass('helper', $name);
            $this->_helper[$name] = new $class();
            if (method_exists($this->_helper[$name], 'setView')) {
                $this->_helper[$name]->setView($this);
            }
        }

        return $this->_helper[$name];
    }
    /**
     * Loads a helper or filter class.
     *
     * @param  string $type The class type ('helper' or 'filter').
     * @param  string $name The base name.
     * @param  string The full class name.
     * @return string class name loaded
     * @throws Zend_View_Exception if unable to load class
     */
    private function _loadClass($type, $name)
    {
        // check to see if name => class mapping exists for helper/filter
        $classLoaded = '_' . $type . 'Loaded';
        $classAccess = '_set' . ucfirst($type) . 'Class';
        if (isset($this->$classLoaded[$name])) {
            return $this->$classLoaded[$name];
        }
        // only look for "$Name.php"
        $file = ucfirst($name) . '.php';

        // do LIFO search for helper
        foreach ($this->_path[$type] as $info) {
            $dir    = $info['dir'];
            $prefix = $info['prefix'];

            $class = $prefix . ucfirst($name);

            if (class_exists($class, false)) {
                $reflection = new ReflectionClass($class);
                $file = $reflection->getFileName();
                $this->$classAccess($name, $class, $file);
                return $class;
            } elseif (Zend_Loader::isReadable($dir . $file)) {
                include_once $dir . $file;

                if (class_exists($class, false)) {
                    $this->$classAccess($name, $class, $dir . $file);
                    return $class;
                }
            }
        }

        require_once 'Zend/View/Exception.php';
        throw new Zend_View_Exception("$type '$name' not found in path", $this);
    }


    /**
     * Получение значения переменной
     *
     * @param string $key The variable name.
     * @return mixed The variable value.
     */
    public function __get($key)
    {
        return;
    }

    /**
     * Позволяет проверять переменные через empty() и isset()
     *
     * @param string $key
     * @return boolean
     */
    public function __isset($key)
    {
        return;
    }

    /**
     * Позволяет удалять свойства объекта через unset()
     *
     * @param string $key
     * @return void
     */
    public function __unset($key)
    {

    }

    /**
     * Присвоение переменных шаблону
     *
     * Позволяет установить значение к определенному ключу или передать массив
     * пар ключ => значение
     *
     * @see __set()
     * @param string|array $spec Ключ или массив пар ключ => значение
     * @param mixed $value (необязательный) Если присваивается значение одной
     * переменной, то через него передается значение переменной
     * @return void
     */
    public function assign($spec, $value = null, $attributes = null)
    {
        if (is_array($spec)){
            $this->_arrayToXml($spec);
        }
        if (is_string($spec)){
            if (is_array($value)){
                $this->xmlWriter->startElement($spec);
                if (!is_null($attributes)){
                    $this->_writeAttributesFromArray($attributes);
                }
                $this->_arrayToXml($value);
                $this->xmlWriter->endElement();
            }elseif (!is_object($value)){
                if (!is_null($attributes)){
                    $this->xmlWriter->startElement($spec);
                    $this->_writeAttributesFromArray($attributes);
                    $this->xmlWriter->text($value);
                    $this->xmlWriter->endElement();
                }else{
                    $this->xmlWriter->writeElement($spec, $value);
                }
            }elseif (($value instanceof $this->_rowsetClass) || is_subclass_of($value, $this->_rowsetClass)){
                $this->xmlWriter->startElement($spec);
                if (!is_null($attributes)){
                    $this->_writeAttributesFromArray($attributes);
                }

                foreach ($value as $item) {
                    $this->assign($this->_defaultNodeName, $item);
                }
                $pager = @$value->getPager();
                if (!empty($pager)){
                    $this->xmlWriter->startElement('pager');
                    $this->_arrayToXml($pager);
                    $this->xmlWriter->endElement();
                }
                $this->xmlWriter->endElement();
            }elseif (($value instanceof $this->_rowClass) || is_subclass_of($value, $this->_rowClass)){
                $this->xmlWriter->startElement($spec);
                if (!is_null($attributes)){
                    $this->_writeAttributesFromArray($attributes);
                }

                $this->_arrayToXml($value->toArray());
                $this->xmlWriter->endElement();
            }
        }
    }

    /**
     * Удаление всех переменных
     *
     * @return void
     */
    public function clearVars()
    {
        $this->_startXml();
    }

    /**
     * Обрабатывает шаблон и возвращает вывод
     *
     * @param string $name Шаблон для обработки
     * @return string Вывод
     */
    public function render($name)
    {
        foreach ($this->_helper as $key=>&$value) {
            $this->assign($value->$key());
        }



        $this->xmlWriter->endElement();

        $xmlString = $this->xmlWriter->outputMemory();

        if (DEBUG_TYPE == 'xml'){
            header( "Content-type: text/xml");
            echo $xmlString;
            die();
        }

        if (DEBUG_TYPE=='sys'){
            $start_xslt_time = microtime(true);
        }


        $xml = new DomDocument();
        $xml->loadXML($xmlString);

        if (extension_loaded('xslcache')){
            $proc = new xsltCache();
            $xsl = $proc->importStylesheet($this->_template_dir.'/'.$name);
        }else {
            $xsl = new DomDocument();
            $xsl->load($this->_template_dir.'/'.$name);
            $proc = new XsltProcessor();
            $xsl = $proc->importStylesheet($xsl);
        }



        $newDom = $proc->transformToDoc($xml);

        $str = $newDom->saveXML();
        $str = str_ireplace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", '', $str);
        $str = str_ireplace('<?xml version="1.0" encoding="utf-8" standalone="yes"?>'.PHP_EOL, '', $str);

        if (DEBUG_TYPE=='sys'){
            $xsl_time = microtime(true) - $start_xslt_time;
            $otime_start = microtime(true);
        }

        print $str;

        if ((!empty(Zend_Registry::get('config')->cache->pages_cache) &&  is_null(DEBUG_TYPE) )|| DEBUG_TYPE=='recache'){

            $servers = Zend_Registry::get('config')->cache->servers->toArray();
            if (!empty($servers)){
                $cache = new Memcache;
                foreach ($servers as $server) {
                    $cache->addServer($server['host'], $server['port'], (bool) $server['persistent']);
                }

                if (DEBUG_TYPE=='recache'){
                    $cache->flush();
                }else{
                    $cache->add($_SERVER['REQUEST_URI'], $str, false, Zend_Registry::get('config')->cache->pages_cache);
                }
            }
        }

        if (DEBUG_TYPE=='sys'){
            $mtime =  microtime(true) - $GLOBALS['start_time'];
            $xsl_time_p = ($xsl_time/$mtime)*100;
            $otime = microtime(true) - $otime_start;
            $otime_p = ($otime/$mtime)*100;
            $qtime = Zend_Registry::get('db')->getProfiler()->getTotalElapsedSecs();
            $qtime_p = ($qtime/$mtime)*100;
            $memory = memory_get_usage() - $GLOBALS['memory_usage'];

            echo '<div style="background-color:#FFFFFF;border-bottom:1px solid #000000;position:absolute;left:0px;top:0px;width:100%;z-index:10000;" id="Layer1"><table background="#ffffff" width="100%" border="0" cellspacing="1" cellpadding="3">
                <tr bgcolor="#0066CC">
                    <td width="300" align="center"><font style="font-family:tahoma;font-size:12px;font-weight:bold;color:#FFFFFF;" color="#ffffff">Name</font></td>
                    <td><font style="font-family:tahoma;font-size:12px;font-weight:bold;color:#FFFFFF;" color="#ffffff">Value</font></td>
                    <td width="20" align="center"  style="color:#ffffff;font-family:Arial;cursor:pointer;" onclick="document.getElementById(\'Layer1\').style.display=\'none\'">Hide</td>
                </tr>';

            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">DB queries count:</td>
                        <td style="padding:5px;" colspan="2">'.Zend_Registry::get('db')->getProfiler()->getTotalNumQueries().'</td>
                    </tr>';
            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">Memory usage:</td>
                        <td style="padding:5px;" colspan="2">'.$memory.'</td>
                    </tr>';
            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">Memory usage peak:</td>
                        <td style="padding:5px;" colspan="2">'.memory_get_peak_usage().'</td>
                    </tr>';

            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">DB queries time:</td>
                        <td style="padding:5px;" colspan="2">'.$qtime.'( '.$qtime_p.'% )</td>
                    </tr>';
            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">XSLT proccesing time:</td>
                        <td style="padding:5px;" colspan="2"> '.$xsl_time.'( '.$xsl_time_p.'% )</td>
                    </tr>';

            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">Output time:</td>
                        <td style="padding:5px;" colspan="2"> '.$otime.'( '.$otime_p.'% )</td>
                    </tr>';

            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">All time:</td>
                        <td style="padding:5px;" colspan="2"> '.$mtime.'</td>
                    </tr>';

            echo '</table></div>';

        }
        if (DEBUG_TYPE == 'sql'){
            $res = Zend_Registry::get('db')->getProfiler()->getQueryProfiles();
            echo '<div style="background-color:#FFFFFF;border-bottom:1px solid #000000;position:absolute;left:0px;top:0px;width:100%;z-index:10000;" id="Layer1"><table background="#ffffff" width="100%" border="0" cellspacing="1" cellpadding="3">
                <tr bgcolor="#0066CC">
                    <td width="40" align="center"><font style="font-family:tahoma;font-size:12px;font-weight:bold;color:#FFFFFF;" color="#ffffff">Time</font></td>
                    <td><font style="font-family:tahoma;font-size:12px;font-weight:bold;color:#FFFFFF;" color="#ffffff">Query</font></td>
                    <td width="20" align="center"  style="color:#ffffff;font-family:Arial;cursor:pointer;" onclick="document.getElementById(\'Layer1\').style.display=\'none\'">Hide</td>
                </tr>';
            foreach ($res as $value) {
                echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">'.$value->getElapsedSecs().'</td>
                        <td style="padding:5px;" colspan="2">'.$value->getQuery().'</td>
                    </tr>';
            }
            echo '<tr style="font-family:tahoma;font-size:11px;font-weight:bold;color:#000000;" bgcolor="#99CCFF">
                        <td style="padding:5px;" width="40" align="center">'.Zend_Registry::get('db')->getProfiler()->getTotalElapsedSecs().'</td>
                        <td style="padding:5px;" colspan="2">Total: '.Zend_Registry::get('db')->getProfiler()->getTotalNumQueries().'</td>
                    </tr></table></div>';
        }
    }

    public function getScriptPaths()
    {
        return array($this->_template_path);
    }

    public function setBasePath($path, $classPrefix = 'Zend_View')
    {
        if (is_readable($path)) {
            $this->_template_dir = $path;
            return;
        }

        throw new Exception('Invalid path provided');

    }

    private function _startXml()
    {
        $this->xmlWriter = new XMLWriter();
        $this->xmlWriter->openMemory();
        $this->xmlWriter->startDocument('1.0', 'utf-8');
        $this->xmlWriter->startElement('page');
    }

    public function addBasePath($path, $classPrefix = 'Zend_View')
    {
        $this->setBasePath($path, $classPrefix);
    }

    /**
     * Adds to the stack of helper paths in LIFO order.
     *
     * @param string|array The directory (-ies) to add.
     * @param string $classPrefix Class prefix to use with classes in this
     * directory; defaults to Zend_View_Helper
     * @return Zend_View_Abstract
     */
    public function addHelperPath($path, $classPrefix = 'Zend_View_Helper_')
    {
        if (!empty($classPrefix) && ('_' != substr($classPrefix, -1))) {
            $classPrefix .= '_';
        }

        $this->_addPath('helper', $path, $classPrefix);
        return $this;
    }

    /**
     * Resets the stack of helper paths.
     *
     * To clear all paths, use Zend_View::setHelperPath(null).
     *
     * @param string|array $path The directory (-ies) to set as the path.
     * @param string $classPrefix The class prefix to apply to all elements in
     * $path; defaults to Zend_View_Helper
     * @return Zend_View_Abstract
     */
    public function setHelperPath($path, $classPrefix = 'Zend_View_Helper_')
    {
        if (!empty($classPrefix) && ('_' != substr($classPrefix, -1))) {
            $classPrefix .= '_';
        }

        $this->_setPath('helper', $path, $classPrefix);
        return $this;
    }

    /**
     * Register helper class as loaded
     *
     * @param  string $name
     * @param  string $class
     * @param  string $file path to class file
     * @return void
     */
    private function _setHelperClass($name, $class, $file)
    {
        $this->_helperLoadedDir[$name] = $file;
        $this->_helperLoaded[$name]    = $class;
    }

    /**
     * Adds paths to the path stack in LIFO order.
     *
     * Zend_View::_addPath($type, 'dirname') adds one directory
     * to the path stack.
     *
     * Zend_View::_addPath($type, $array) adds one directory for
     * each array element value.
     *
     * In the case of filter and helper paths, $prefix should be used to
     * specify what class prefix to use with the given path.
     *
     * @param string $type The path type ('script', 'helper', or 'filter').
     * @param string|array $path The path specification.
     * @param string $prefix Class prefix to use with path (helpers and filters
     * only)
     * @return void
     */
    private function _addPath($type, $path, $prefix = null)
    {
        foreach ((array) $path as $dir) {
            // attempt to strip any possible separator and
            // append the system directory separator
            $dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $dir);
            $dir = rtrim($dir, DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR)
            . DIRECTORY_SEPARATOR;

            switch ($type) {
                case 'script':
                    // add to the top of the stack.
                    array_unshift($this->_path[$type], $dir);
                    break;
                case 'filter':
                case 'helper':
                default:
                    // add as array with prefix and dir keys
                    @array_unshift($this->_path[$type], array('prefix' => $prefix, 'dir' => $dir));
                    break;
            }

        }
    }
    /**
     * Get full path to a helper class file specified by $name
     *
     * @param  string $name
     * @return string|false False on failure, path on success
     */
    public function getHelperPath($name)
    {
        if (isset($this->_helperLoadedDir[$name])) {
            return $this->_helperLoadedDir[$name];
        }

        try {
            $this->_loadClass('helper', $name);
            return $this->_helperLoadedDir[$name];
        } catch (Zend_View_Exception $e) {
            return false;
        }
    }

    /**
     * Returns an array of all currently set helper paths
     *
     * @return array
     */
    public function getHelperPaths()
    {
        return $this->_getPaths('helper');
    }

    /**
     * Return all paths for a given path type
     *
     * @param string $type The path type  ('helper', 'filter', 'script')
     * @return array
     */
    private function _getPaths($type)
    {
        return $this->_path[$type];
    }


    private function _writeAttributesFromArray($array)
    {
        if (!empty($array)&&is_array($array)){
            foreach ($array as $key=>$value){
                if (!is_string($key)){
                    $this->xmlWriter->writeAttribute($this->_defaultAttrName, $value);
                }else{
                    $this->xmlWriter->writeAttribute($key, $value);
                }
            }
        }
    }

    /**
     * Creates XML from array
       *
     * @param array $array
     */
    protected function _arrayToXml($array, $type = self::KEY_AS_TAG){
        if (!empty($array)){
            $i = 0;

            foreach ($array as $key => &$value) {
                if (is_array($value)){
                    if (is_string($key)&&(!empty($key))){
                        if ($type==self::KEY_AS_TAG ){
                            $this->xmlWriter->startElement($key);
                            if (!is_null(@$value['__attributes'])){
                                $this->_writeAttributesFromArray(@$value['__attributes']);
                                unset($value['__attributes']);
                            }
                            $this->_arrayToXml($value);
                            $this->xmlWriter->endElement();
                        }elseif ($type==self::KEY_AS_ATTRIBUTE ){
                            $this->xmlWriter->startElement($this->_defaultNodeName);
                            if (!is_null(@$value['__attributes'])){
                                $this->_writeAttributesFromArray(@$value['__attributes']);
                                unset($value['__attributes']);
                            }
                            $this->xmlWriter->writeAttribute('name', $key);
                            $this->xmlWriter->writeAttribute('id', $i);
                            $this->_arrayToXml($value, $type);
                            $this->xmlWriter->endElement();
                        }
                    }else{
                        $this->xmlWriter->startElement($this->_defaultNodeName);
                        $this->xmlWriter->writeAttribute('id', $key);
                        if (!is_null(@$value['__attributes'])){
                            $this->_writeAttributesFromArray(@$value['__attributes']);
                            unset($value['__attributes']);
                        }
                        $this->_arrayToXml($value, $type);
                        $this->xmlWriter->endElement();
                    }
                }elseif ($value instanceof Exception){
                    $value = $value->getMessages();
                    $this->_arrayToXml($value);
                }else{
                    if (is_string($key)){
                        if ($type==self::KEY_AS_TAG ){
                            $this->xmlWriter->writeElement($key, $value);
                        }elseif ($type==self::KEY_AS_ATTRIBUTE ){
                            $this->xmlWriter->startElement($this->_defaultNodeName);
                            $this->xmlWriter->writeAttribute('name', $key);
                            $this->xmlWriter->writeAttribute('id',$i);
                            $this->xmlWriter->text($value);
                            $this->xmlWriter->endElement();
                        }
                    }elseif (!is_object($value)){
                        $this->xmlWriter->startElement($this->_defaultNodeName);
                        $this->xmlWriter->writeAttribute('id', $key);
                        $this->xmlWriter->text($value);
                        $this->xmlWriter->endElement();
                    }
                }
                $i++;
            }
        }
    }
}
/**
 *

 * */